package ryusty.robot.init;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import robocode.AdvancedRobot;
import robocode.HitByBulletEvent;
import robocode.ScannedRobotEvent;
import robocode.util.Utils;
import ryusty.robot.utils.RobotUtility;

public class FireBotTest extends AdvancedRobot {

	public static int BINS = 47;
	public static double surfStats[] = new double[BINS];
	public Point2D.Double myLocation;
	public Point2D.Double enemyLocation;

	public ArrayList enemyWaves;
	public ArrayList surfDirections;
	public ArrayList surfAbsBearings;

	public static double oppEnergy = 100.0;

	/**
	 * For Wall Smoting
	 */
	public static Rectangle2D.Double fieldRect = new java.awt.geom.Rectangle2D.Double(
			18, 18, 764, 564);
	public static double WALL_STICK = 160;

	public void run() {
		enemyWaves = new ArrayList();
		surfDirections = new ArrayList();
		surfAbsBearings = new ArrayList();

		setAdjustGunForRobotTurn(true);
		setAdjustRadarForGunTurn(true);

		do {
			turnRadarRightRadians(Double.POSITIVE_INFINITY);
		} while (true);
	}

	public void onScannedRobot(ScannedRobotEvent e) {
		myLocation = new Point2D.Double(getX(), getY());

		double lateralVelocity = getVelocity()
				* Math.sin(e.getBearingRadians());
		double absBearing = e.getBearingRadians() + getHeadingRadians();

		setTurnRadarRightRadians(Utils.normalRelativeAngle(absBearing
				- getRadarHeadingRadians()) * 2);

		surfDirections.add(0, new Integer((lateralVelocity >= 0) ? 1 : -1));
		surfAbsBearings.add(0, new Double(absBearing + Math.PI));

		double bulletPower = oppEnergy - e.getEnergy();
		if (bulletPower < 3.01 && bulletPower > 0.09
				&& surfDirections.size() > 2) {
			RobotUtility ew = new RobotUtility();
			ew.fireTime = getTime() - 1;
			ew.bulletVelocity = RobotUtility.bulletVelocity(bulletPower);
			ew.distanceTraveled = RobotUtility.bulletVelocity(bulletPower);
			ew.direction = ((Integer) surfDirections.get(2)).intValue();
			ew.directAngle = ((Double) surfAbsBearings.get(2)).doubleValue();
			ew.fireLocation = (Point2D.Double) enemyLocation.clone(); // last
																		// tick

			enemyWaves.add(ew);
		}

		oppEnergy = e.getEnergy();

		// update after EnemyWave detection, because that needs the previous
		// enemy location as the source of the wave
		enemyLocation = RobotUtility.project(myLocation, absBearing,
				e.getDistance());

		updateWaves();
		doSurfing();

		// gun code would go here...

	}

	public void updateWaves() {
		for (int x = 0; x < enemyWaves.size(); x++) {
			RobotUtility ew = (RobotUtility) enemyWaves.get(x);

			ew.distanceTraveled = (getTime() - ew.fireTime) * ew.bulletVelocity;
			if (ew.distanceTraveled > myLocation.distance(ew.fireLocation) + 50) {
				enemyWaves.remove(x);
				x--;
			}

		}
	}

	public RobotUtility getClosestSurfableWave() {
		double closestDistance = 50000; // I juse use some very big number here
		RobotUtility surfWave = null;

		for (int x = 0; x < enemyWaves.size(); x++) {
			RobotUtility ew = (RobotUtility) enemyWaves.get(x);
			double distance = myLocation.distance(ew.fireLocation)
					- ew.distanceTraveled;

			if (distance > ew.bulletVelocity && distance < closestDistance) {
				surfWave = ew;
				closestDistance = distance;
			}
		}

		return surfWave;
	}

	// Given the EnemyWave that the bullet was on, and the point where we
	// were hit, calculate the index into our stat array for that factor.
	public static int getFactorIndex(RobotUtility ew,
			Point2D.Double targetLocation) {
		double offsetAngle = (RobotUtility.absoluteBearing(ew.fireLocation,
				targetLocation) - ew.directAngle);
		double factor = Utils.normalRelativeAngle(offsetAngle)
				/ RobotUtility.maxEscapeAngle(ew.bulletVelocity) * ew.direction;

		return (int) RobotUtility.limit(0, (factor * ((BINS - 1) / 2))
				+ ((BINS - 1) / 2), BINS - 1);
	}

	// Given the EnemyWave that the bullet was on, and the point where we
	// were hit, update our stat array to reflect the danger in that area.
	public void logHit(RobotUtility ew, Point2D.Double targetLocation) {
		int index = getFactorIndex(ew, targetLocation);

		for (int x = 0; x < BINS; x++) {
			// for the spot bin that we were hit on, add 1;
			// for the bins next to it, add 1 / 2;
			// the next one, add 1 / 5; and so on...
			surfStats[x] += 1.0 / (Math.pow(index - x, 2) + 1);
		}
	}

	public void onHitByBullet(HitByBulletEvent e) {
		// If the _enemyWaves collection is empty, we must have missed the
		// detection of this wave somehow.
		if (!enemyWaves.isEmpty()) {
			Point2D.Double hitBulletLocation = new Point2D.Double(e.getBullet()
					.getX(), e.getBullet().getY());
			RobotUtility hitWave = null;

			// look through the EnemyWaves, and find one that could've hit us.
			for (int x = 0; x < enemyWaves.size(); x++) {
				RobotUtility ew = (RobotUtility) enemyWaves.get(x);

				if (Math.abs(ew.distanceTraveled
						- myLocation.distance(ew.fireLocation)) < 50
						&& Math.abs(RobotUtility.bulletVelocity(e.getBullet()
								.getPower()) - ew.bulletVelocity) < 0.001) {
					hitWave = ew;
					break;
				}
			}

			if (hitWave != null) {
				logHit(hitWave, hitBulletLocation);

				// We can remove this wave now, of course.
				enemyWaves.remove(enemyWaves.lastIndexOf(hitWave));
			}
		}
	}

	public Point2D.Double predictPosition(RobotUtility surfWave, int direction) {
		Point2D.Double predictedPosition = (Point2D.Double) myLocation.clone();
		double predictedVelocity = getVelocity();
		double predictedHeading = getHeadingRadians();
		double maxTurning, moveAngle, moveDir;

		int counter = 0; // number of ticks in the future
		boolean intercepted = false;

		do { // the rest of these code comments are rozu's
			moveAngle = RobotUtility.wallSmoothing(
					predictedPosition,
					RobotUtility.absoluteBearing(surfWave.fireLocation,
							predictedPosition) + (direction * (Math.PI / 2)),
					direction, fieldRect, WALL_STICK)
					- predictedHeading;
			moveDir = 1;

			if (Math.cos(moveAngle) < 0) {
				moveAngle += Math.PI;
				moveDir = -1;
			}

			moveAngle = Utils.normalRelativeAngle(moveAngle);

			// maxTurning is built in like this, you can't turn more then this
			// in one tick
			maxTurning = Math.PI / 720d
					* (40d - 3d * Math.abs(predictedVelocity));
			predictedHeading = Utils.normalRelativeAngle(predictedHeading
					+ RobotUtility.limit(-maxTurning, moveAngle, maxTurning));

			// this one is nice ;). if predictedVelocity and moveDir have
			// different signs you want to breack down
			// otherwise you want to accelerate (look at the factor "2")
			predictedVelocity += (predictedVelocity * moveDir < 0 ? 2 * moveDir
					: moveDir);
			predictedVelocity = RobotUtility.limit(-8, predictedVelocity, 8);

			// calculate the new predicted position
			predictedPosition = RobotUtility.project(predictedPosition,
					predictedHeading, predictedVelocity);

			counter++;

			if (predictedPosition.distance(surfWave.fireLocation) < surfWave.distanceTraveled
					+ (counter * surfWave.bulletVelocity)
					+ surfWave.bulletVelocity) {
				intercepted = true;
			}
		} while (!intercepted && counter < 500);

		return predictedPosition;
	}

	public double checkDanger(RobotUtility surfWave, int direction) {
		int index = getFactorIndex(surfWave,
				predictPosition(surfWave, direction));

		return surfStats[index];
	}

	public void doSurfing() {
		RobotUtility surfWave = getClosestSurfableWave();

		if (surfWave == null) {
			return;
		}

		double dangerLeft = checkDanger(surfWave, -1);
		double dangerRight = checkDanger(surfWave, 1);

		double goAngle = RobotUtility.absoluteBearing(surfWave.fireLocation,
				myLocation);
		if (dangerLeft < dangerRight) {
			goAngle = RobotUtility.wallSmoothing(myLocation, goAngle
					- (Math.PI / 2), -1, fieldRect, WALL_STICK);
		} else {
			goAngle = RobotUtility.wallSmoothing(myLocation, goAngle
					+ (Math.PI / 2), 1, fieldRect, WALL_STICK);
		}

		RobotUtility.setBackAsFront(this, goAngle);
	}

}
